/**
 * Copyright (c) 2011 Daniel Centore
 *
 * ex: set filetype=java expandtab tabstop=4 shiftwidth=4 :
 * 
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * This code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * This code is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by the Free Software Foundation.
 */
package wiiWired.controller.mote;

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;

import motej.Extension;
import motej.Mote;
import motej.event.CoreButtonEvent;
import motej.event.CoreButtonListener;
import motej.event.ExtensionEvent;
import motej.event.ExtensionListener;
import motej.event.MoteDisconnectedEvent;
import motej.event.MoteDisconnectedListener;
import motej.request.ReportModeRequest;
import motejx.extensions.nunchuk.Nunchuk;
import motejx.extensions.nunchuk.NunchukButtonEvent;
import motejx.extensions.nunchuk.NunchukButtonListener;
import wiiWired.controller.console.RaceTableModel;
import wiiWired.controller.data.NameGetter;
import wiiWired.controller.mote.buttons.Button;
import wiiWired.controller.mote.buttons.ButtonController;
import wiiWired.controller.mote.buttons.ButtonListener;

import com.intel.bluetooth.BlueCoveImpl;

/**
 * Class which controls a single WiiMote
 * 
 * @author Daniel Centore
 * 
 */
public class MoteController implements ExtensionListener, CoreButtonListener, NunchukButtonListener, ButtonListener, Comparable<MoteController>
{

	public static final int WARNING_BATTERY_LEVEL = 20; //warning level for battery

	private Mote mote; //our remote
	private Nunchuk nunchuk; //our nunchuk
	private Object dongle; //what dongle are we connected to?
	private boolean connected; //is this wiiMote connected?
	private boolean disconnecting; //are we in the process of disconnecting?
	private boolean isBPressed; //is the B Button pressed?

	private ButtonController controller; //does transparent button handling

	//	private long time = -1;			//current time (-1 = not stopped)
	//	private long nunchukTime = -1;	//current time (-1 = not stopped)
	//	private List<Long> times = new ArrayList<Long>();
	//	private List<Long> nunchukTimes = new ArrayList<Long>();

	private int lights; //what mode are the lights in? statics below.
	public static final int TIMING = 0; //the lights rotating
	public static final int BATTERY = 1; //the battery level

	private MoteDisconnectedListener<Mote> disconnectedListener;

	@SuppressWarnings("unchecked")
	public MoteController(Mote m)
	{
		dongle = m.getDongle();
		lights = -1;
		connected = true;
		disconnecting = false;

		mote = m;
		nunchuk = null;
		controller = new ButtonController(this);

		m.addExtensionListener(this);
		m.addCoreButtonListener(this);

		m.setPlayerLeds(new boolean[] { false, false, false, false });

		ControllerModel.addMote(this);

		mote.addMoteDisconnectedListener(disconnectedListener = new MoteDisconnectedListener()
		{
			@Override
			public void moteDisconnected(MoteDisconnectedEvent evt)
			{
				System.out.println("DISCONNECTED");
				connected = false;

				lights = -1;
			}
		});

		mote.rumble(1000);

		new Thread()
		{
			public void run()
			{
				BlueCoveImpl.setThreadBluetoothStackID(dongle);
				while (true)
				{
					MoteController.this.process();

					try
					{
						Thread.sleep(100);
					} catch (InterruptedException e)
					{
					}
				}
			}
		}.start();

		//		JBWindow.fireUpdateRequired(true)
		try
		{
			Thread.sleep(1000); //give it a change to connect (otherwise we just return :-o)
		} catch (InterruptedException e)
		{
			e.printStackTrace();
		}

		if (TimerController.isTiming())
			setTiming(true);
	}

	@Override
	public void extensionConnected(ExtensionEvent arg0)
	{
		final Extension ext = arg0.getExtension();

		if (ext instanceof Nunchuk) //was a Nunchuk connected?
		{
			nunchuk = (Nunchuk) ext;
			nunchuk.addNunchukButtonListener(this);

			mote.rumble(1000);

			mote.setReportMode(ReportModeRequest.DATA_REPORT_0x32); //core buttons and 8-bits for extension (nunchuk)
		}
	}

	@Override
	public void extensionDisconnected(ExtensionEvent arg0)
	{
		if (nunchuk != null) //if nunchuk was attached...
			nunchuk = null;

		mote.setReportMode(ReportModeRequest.DATA_REPORT_0x30); //core buttons only (NOTE: leaving as 0x32 DOES NOT work!)
	}

	@Override
	public void buttonPressed(CoreButtonEvent arg0)
	{
		controller.buttonPressed(arg0);
	}

	@Override
	public void buttonPressed(NunchukButtonEvent arg0)
	{
		controller.buttonPressed(arg0);
	}

	/**
	 * Returns the remotes battery level as a number from 0-100
	 * @return
	 */
	public int getBatteryLevel()
	{
		try
		{
			return mote.getStatusInformationReport().getBatteryLevel();
		} catch (NullPointerException e)
		{
			return 100;
		}
	}

	/**
	 * Called very often. Used for updating lights and stuff.
	 */
	public void process()
	{
		if (lights == BATTERY)
		{
			int batt = getBatteryLevel();
			batt /= 25;

			boolean[] led = new boolean[4];
			for (int i = 0; i < batt; i++)
				led[i] = true;

			mote.setPlayerLeds(led);
		}
		else if (lights == TIMING)
			mote.setPlayerLeds(TimerLights.getLights());
	}

	/**
	 * Debug printing method
	 * @param s
	 */
	public void println(String s)
	{
		//		System.out.println("["+mote.getBluetoothAddress()+"]: "+s);
	}

	@Override
	/**
	 * Called when a button is pressed
	 * The names kinda decieving.
	 */
	public void buttonPressed(int button)
	{
		println(button + " pressed");

		if (button == Button.B_BUTTON)
		{
			isBPressed = true;

			if (lights == TIMING)
			{
				long time = TimerController.getCurrentTime();

				if (time < 1000)
					return;

				TimerController.getCurrentRace().addMoteTime(getBluetoothAddress(), time);

				//				moteTimes += "\n" + RaceTableModel.formatTime(time);
				//				times.add(time);

				//				TimerController.getCurrentRace().setMoteTimes(mote.getBluetoothAddress(), moteTimes.substring(1));
			}
		}
		else if (button == Button.Z_TRIGGER)
		{
			if (lights == TIMING)
			{
				long time = TimerController.getCurrentTime();

				TimerController.getCurrentRace().addNunchukTime(getBluetoothAddress(), time);

				//				nunchukTimes.add(nunchukTime);
				//				nunchukTimes += "\n" + RaceTableModel.formatTime(nunchukTime);
				//				TimerController.getCurrentRace().setNunchukTimes(mote.getBluetoothAddress(), nunchukTimes.substring(1));
			}
		}
		else if (button == Button.HOME_BUTTON)
		{
			disconnecting = true;
			mote.disconnect();
			disconnectedListener.moteDisconnected(null);
		}

		if (lights != TIMING) //so we dont interrupt
		{
			if (button == Button.DPAD_UP)
				lights = BATTERY;
			else if (button == Button.DPAD_LEFT)
			{
				lights = -1;
				mote.setPlayerLeds(new boolean[4]);
			}
		}
	}

	@Override
	/**
	 * Called when a button is released
	 */
	public void buttonReleased(int button)
	{
		println(button + " released");

		if (button == Button.B_BUTTON)
		{
			isBPressed = false;
		}
	}

	/**
	 * Sets whether or not we are timing
	 * @param b True if yes, false otherwise
	 */
	public void setTiming(boolean b)
	{
		if (connected == false)
			return;

		if (b)
			lights = TIMING;
		else
		{
			lights = -1;
			mote.setPlayerLeds(new boolean[4]);
		}
	}

	//	public long[] getTimes()
	//	{
	//		Object[] o = times.toArray();
	//		long[] l = new long[o.length];
	//
	//		for (int i = 0; i < o.length; i++)
	//			l[i] = (long) ((Long) o[i]);
	//
	//		return l;
	//	}
	//
	//	public long[] getNunchukTimes()
	//	{
	//		Object[] o = nunchukTimes.toArray();
	//		long[] l = new long[o.length];
	//
	//		for (int i = 0; i < o.length; i++)
	//			l[i] = (long) ((Long) o[i]);
	//
	//		return l;
	//	}

	/**
	 * Returns a name for this remote (for the column names usually)
	 * @return
	 */
	public String getName()
	{
		int level = getBatteryLevel();

		return NameGetter.getName(mote.getBluetoothAddress()) + " (" + (level >= 0 ? level : "?") + "%)";
	}

	/**
	 * Returns a name for the nunchuk
	 * @return
	 */
	public String getNunchukName()
	{
		if (nunchuk == null)
			return "-";
		else
			return "Nunchuk";
	}

	/**
	 * Resets all of the times
	 */
	public void resetTime()
	{
		//		time = -1;
		//		nunchukTime = -1;
		//		times = new ArrayList<Long>();
		//		nunchukTimes = new ArrayList<Long>();
		//		moteTimes = "";
		//		nunchukTimes = "";
	}

	/**
	 * Returns the BT address of this remote
	 * @return
	 */
	public String getBluetoothAddress()
	{
		return mote.getBluetoothAddress();
	}

	/**
	 * Gets the color this mote should be represented by
	 * @return
	 */
	public Color getColor()
	{
		if (!connected)
			return Color.gray;
		else
		{
			if (disconnecting)
				return Color.yellow;
			else if (isBPressed)
				return Color.magenta;
			else if (lights == TIMING)
				return Color.cyan;
			else if (getBatteryLevel() < WARNING_BATTERY_LEVEL && getBatteryLevel() >= 0)
				return Color.red;
			else
				return Color.green;
		}
	}

	/**
	 * Returns true if we have a nunchuk, false otherwise
	 * @return
	 */
	public boolean hasNunchuk()
	{
		return (nunchuk != null);
	}

	@Override
	public int compareTo(MoteController arg0)
	{
		return (this.getName().compareTo(arg0.getName()));
	}

}
